/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the AndroidAnnotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.internal.process;

import com.helger.jcodemodel.JCodeModel;

import org.ohosannotations.handler.AnnotationHandler;
import org.ohosannotations.handler.GeneratingAnnotationHandler;
import org.ohosannotations.holder.GeneratedClassHolder;
import org.ohosannotations.internal.InternalOhosAnnotationsEnvironment;
import org.ohosannotations.internal.exception.ProcessingException;
import org.ohosannotations.internal.model.AnnotationElements;
import org.ohosannotations.internal.model.AnnotationElements.AnnotatedAndRootElements;
import org.ohosannotations.logger.Logger;
import org.ohosannotations.logger.LoggerFactory;

import java.util.Set;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.TypeElement;

/**
 * 模型的处理器
 *
 * @author dev
 * @since 2021-07-22
 */
public class ModelProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(ModelProcessor.class);

    /**
     * 过程的结果
     *
     * @author dev
     * @since 2021-07-22
     */
    public static class ProcessResult {
        /**
         * 代码模型
         */
        public final JCodeModel codeModel;
        /**
         * 原始元素
         */
        public final OriginatingElements originatingElements;

        /**
         * 过程的结果
         *
         * @param codeModel 代码模型
         * @param originatingElements 原始元素
         */
        public ProcessResult(JCodeModel codeModel, OriginatingElements originatingElements) {
            this.codeModel = codeModel;
            this.originatingElements = originatingElements;
        }
    }

    private final InternalOhosAnnotationsEnvironment environment;

    /**
     * 模型的处理器
     *
     * @param environment 环境
     */
    public ModelProcessor(InternalOhosAnnotationsEnvironment environment) {
        this.environment = environment;
    }

    /**
     * 过程
     *
     * @param validatedModel 验证模型
     * @return {@link ProcessResult}
     * @throws Exception 异常
     */
    public ProcessResult process(AnnotationElements validatedModel) throws Exception {
        ProcessHolder processHolder = new ProcessHolder(environment.getProcessingEnvironment());

        environment.setProcessHolder(processHolder);

        LOGGER.info("Processing root elements");

        /*
         * We generate top classes then inner classes, then inner classes of inner
         * classes, etc... until there is no more classes to generate.
         */
        while (generateElements(validatedModel, processHolder)) {
        }

        LOGGER.info("Processing enclosed elements");

        for (AnnotationHandler annotationHandler : environment.getDecoratingHandlers()) {
            if (!annotationHandler.isEnabled()) {
                continue;
            }
            String annotationName = annotationHandler.getTarget();

            /*
             * For ancestors, the annotationHandler manipulates the annotated elements, but
             * uses the holder for the root element
             */
            Set<AnnotatedAndRootElements> ancestorAnnotatedElements
                = validatedModel.getAncestorAnnotatedElements(annotationName);

            if (!ancestorAnnotatedElements.isEmpty()) {
                LOGGER.debug("Processing enclosed elements with {}: {}",
                    annotationHandler.getClass().getSimpleName(), ancestorAnnotatedElements);
            }

            for (AnnotatedAndRootElements elements : ancestorAnnotatedElements) {
                GeneratedClassHolder holder = processHolder.getGeneratedClassHolder(elements.rootTypeElement);
                /*
                 * Annotations coming from ancestors may be applied to root elements that are
                 * not validated, and therefore not available.
                 */
                if (holder != null) {
                    processThrowing(annotationHandler, elements.annotatedElement, holder);
                }
            }

            modelProcessor(validatedModel, processHolder, annotationHandler, annotationName);
        }

        return new ProcessResult(
            processHolder.codeModel(),
            processHolder.getOriginatingElements());
    }

    private void modelProcessor(AnnotationElements validatedModel, ProcessHolder processHolder,
        AnnotationHandler annotationHandler, String annotationName) throws ProcessingException {
        Set<? extends Element> rootAnnotatedElements = validatedModel.getRootAnnotatedElements(annotationName);

        for (Element annotatedElement : rootAnnotatedElements) {
            Element enclosingElement;
            if (annotatedElement instanceof TypeElement) {
                enclosingElement = annotatedElement;
            } else {
                enclosingElement = annotatedElement.getEnclosingElement();
                /*
                 * we are processing a method parameter
                 */
                if (enclosingElement instanceof ExecutableElement) {
                    enclosingElement = enclosingElement.getEnclosingElement();
                }
            }

            /*
             * We do not generate code for elements belonging to abstract classes, because
             * the generated classes are final anyway
             */
            if (!isAbstractClass(enclosingElement)) {
                GeneratedClassHolder holder = processHolder.getGeneratedClassHolder(enclosingElement);

                /*
                 * The holder can be null if the annotated holder class is already invalidated.
                 */
                if (holder != null) {
                    processThrowing(annotationHandler, annotatedElement, holder);
                }
            } else {
                LOGGER.trace("Skip element {} because enclosing "
                    + "element {} is abstract", annotatedElement, enclosingElement);
            }
        }
    }

    /**
     * 过程把
     *
     * @param handler 处理程序
     * @param element 元素
     * @param generatedClassHolder 生成的类人
     * @throws ProcessingException 处理异常
     */
    private <T extends GeneratedClassHolder> void processThrowing(AnnotationHandler<T> handler,
        Element element, T generatedClassHolder) throws ProcessingException {
        try {
            handler.process(element, generatedClassHolder);
        } catch (Exception e) {
            throw new ProcessingException(e, element);
        }
    }

    /**
     * 是抽象类
     *
     * @param annotatedElement 带注释的元素
     * @return boolean
     */
    private boolean isAbstractClass(Element annotatedElement) {
        if (annotatedElement instanceof TypeElement) {
            TypeElement typeElement = (TypeElement) annotatedElement;

            return typeElement.getKind() == ElementKind.CLASS && typeElement
                .getModifiers().contains(Modifier.ABSTRACT);
        } else {
            return false;
        }
    }

    /**
     * 生成的元素
     *
     * @param validatedModel 验证模型
     * @param processHolder 过程持有人
     * @return boolean
     * @throws Exception 异常
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private boolean generateElements(AnnotationElements validatedModel,
        ProcessHolder processHolder) throws Exception {
        boolean isElementRemaining = false;
        Set<? extends Element> validatedElements = validatedModel.getAllElements();
        for (GeneratingAnnotationHandler generatingAnnotationHandler : environment.getGeneratingHandlers()) {
            if (!generatingAnnotationHandler.isEnabled()) {
                continue;
            }
            String annotationName = generatingAnnotationHandler.getTarget();
            Set<? extends Element> annotatedElements = validatedModel.getRootAnnotatedElements(annotationName);

            if (!annotatedElements.isEmpty()) {
                LOGGER.debug("Processing root elements {}: {}",
                    generatingAnnotationHandler.getClass().getSimpleName(), annotatedElements);
            }

            for (Element annotatedElement : annotatedElements) {
                /*
                 * We do not generate code for abstract classes, because the generated classes
                 * are final anyway (we do not want anyone to extend them).
                 */
                if (!isAbstractClass(annotatedElement)) {
                    if (processHolder.getGeneratedClassHolder(annotatedElement) == null) {
                        TypeElement typeElement = (TypeElement) annotatedElement;
                        Element enclosingElement = annotatedElement.getEnclosingElement();

                        if (typeElement.getNestingKind() == NestingKind.MEMBER
                            && processHolder.getGeneratedClassHolder(enclosingElement) == null) {
                            if (validatedElements.contains(enclosingElement)) {
                                isElementRemaining = true;
                            } else {
                                LOGGER.error(annotatedElement, "Enclosing element {} " +
                                    "has not been successfully validated", enclosingElement);
                            }
                        } else {
                            GeneratedClassHolder generatedClassHolder = generatingAnnotationHandler
                                .createGeneratedClassHolder(environment, typeElement);
                            processHolder.put(annotatedElement, generatedClassHolder);
                            generatingAnnotationHandler.process(annotatedElement, generatedClassHolder);
                        }
                    }
                } else {
                    LOGGER.trace("Skip element {} because it's abstract", annotatedElement);
                }
            }
            /*
             * We currently do not take into account class annotations from ancestors. We
             * should careful design the priority rules first.
             */
        }
        return isElementRemaining;
    }
}
